package com.facebook.drawee.controller;

import android.graphics.drawable.Animatable;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.ViewConfiguration;

import com.facebook.common.internal.Preconditions;
import com.facebook.common.logging.FLog;
import com.facebook.datasource.AbstractDataSource;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.drawee.components.DeferredReleaser;
import com.facebook.drawee.components.DraweeEventTracker;
import com.facebook.drawee.components.DraweeEventTracker.Event;
import com.facebook.drawee.components.RetryManager;
import com.facebook.drawee.gestures.GestureDetector;
import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.drawee.interfaces.DraweeHierarchy;
import com.facebook.drawee.interfaces.SettableDraweeHierarchy;

import java.util.concurrent.Executor;

import javax.annotation.Nullable;
import javax.annotation.concurrent.NotThreadSafe;

@NotThreadSafe
public abstract class AbstractDraweeController<T, INFO> implements GestureDetector.ClickListener, DraweeController, DeferredReleaser.Releasable {
    final static class InternalForwardingListener<INFO> extends ForwardingControllerListener<INFO> {
        InternalForwardingListener() {
        }
    }

    @Nullable
    RetryManager mRetryManager;
    @Nullable
    GestureDetector mGestureDetector;
    private static final Class<?> TAG = AbstractDraweeController.class;
    private final DraweeEventTracker mEventTracker = new DraweeEventTracker();
    private final DeferredReleaser mDeferredReleaser;
    private final Executor mUiThreadImmediateExecutor;
    @Nullable
    private ControllerListener<INFO> mControllerListener;
    @Nullable
    private SettableDraweeHierarchy mSettableDraweeHierarchy;
    private String mId;
    private Object mCallerContext;
    private boolean mIsAttached;
    private boolean mIsRequestSubmitted;
    private boolean mHasFetchFailed;
    @Nullable
    private AbstractDataSource mDataSource;
    @Nullable
    private T mFetchedImage;
    @Nullable
    private Drawable mDrawable;

    public AbstractDraweeController(DeferredReleaser deferredReleaser,
                                    Executor uiThreadImmediateExecutor,
                                    String id,
                                    Object callerContext) {
        this.mDeferredReleaser = deferredReleaser;
        this.mUiThreadImmediateExecutor = uiThreadImmediateExecutor;
        this.initialize(id, callerContext, true);
    }

    public final void initialize(String id, Object callerContext, boolean justConstructed) {
        this.mEventTracker.recordEvent(Event.ON_INIT_CONTROLLER);
        if (!justConstructed && this.mDeferredReleaser != null) {
            this.mDeferredReleaser.cancelDeferredRelease(((DeferredReleaser.Releasable) this));
        }

        this.mIsAttached = false;
        this.releaseFetch();
        if (this.mRetryManager != null) {
            this.mRetryManager.init();
        }

        if (this.mGestureDetector != null) {
            this.mGestureDetector.init();
            this.mGestureDetector.mClickListener = this;
        }

        if ((this.mControllerListener instanceof AbstractDraweeController.InternalForwardingListener)) {
            ((InternalForwardingListener) this.mControllerListener).clearListeners();
        } else {
            this.mControllerListener = null;
        }

        if (this.mSettableDraweeHierarchy != null) {
            this.mSettableDraweeHierarchy.reset();
            this.mSettableDraweeHierarchy.setControllerOverlay();
            this.mSettableDraweeHierarchy = null;
        }

        if (FLog.isLoggable()) {
            FLog.v(TAG, "controller %x %s -> %s: initialize", Integer.valueOf(System.identityHashCode(this)), this.mId, id);
        }

        this.mId = id;
        this.mCallerContext = callerContext;
    }

    void onNewResultInternal(String id,
                             AbstractDataSource<T> dataSource,
                             T image,
                             float progress,
                             boolean isFinished,
                             boolean wasImmediate) {
        if (!isExpectedDataSource(id, dataSource)) {
            logMessageAndImage("ignore_old_datasource @ onNewResult", image);
            dataSource.close();
            return;
        }
        Event event = isFinished ? Event.ON_DATASOURCE_RESULT : Event.ON_DATASOURCE_RESULT_INT;
        mEventTracker.recordEvent(event);
        Drawable drawable;
        try {
            drawable = createDrawable(image);
        } catch (Exception e) {
            logMessageAndImage("drawable_failed @ onNewResult", image);
            onFailureInternal(id, dataSource, ((Throwable) e), isFinished);
            return;
        }
        mFetchedImage = image;
        mDrawable = drawable;
        if (isFinished) {
            try {
                logMessageAndImage("set_final_result @ onNewResult", image);
                mDataSource = null;
                mSettableDraweeHierarchy.setImage(drawable, 1f, wasImmediate);
                INFO info = getImageInfo(image);
                Animatable animatable;
                if (mDrawable instanceof Animatable) {
                    animatable = (Animatable) mDrawable;
                } else {
                    animatable = null;
                }
                getControllerListener().onFinalImageSet(id, info, animatable);
            } catch (Throwable throwable) {
                if (mFetchedImage != null && mFetchedImage != image) {
                    logMessageAndImage("release_previous_result @ onNewResult", mFetchedImage);
                }
                throw throwable;
            }
        } else {
            logMessageAndImage("set_intermediate_result @ onNewResult", image);
            mSettableDraweeHierarchy.setImage(drawable, progress, wasImmediate);
            getControllerListener().onIntermediateImageSet(id, getImageInfo(image));
        }

        if (mFetchedImage != null && mFetchedImage != image) {
            logMessageAndImage("release_previous_result @ onNewResult", mFetchedImage);
        }
    }

    private boolean isExpectedDataSource(String id, AbstractDataSource<T> dataSource) {
        return id.equals(this.mId) && dataSource == this.mDataSource && this.mIsRequestSubmitted;
    }

    private void logMessageAndImage(String messageAndMethod, T image) {
        if (FLog.isLoggable()) {
            FLog.a(TAG, "controller %x %s: %s: image: %s %x",
                    System.identityHashCode(this),
                    mId,
                    messageAndMethod,
                    image != null ? image.getClass().getSimpleName() : "<null>",
                    this.getImageHash(image));
        }
    }

    public abstract Drawable createDrawable(T t);

    private void onFailureInternal(String arg3, AbstractDataSource dataSource, Throwable throwable, boolean z) {
        if (!this.isExpectedDataSource(arg3, dataSource)) {
            this.logMessageAndFailure("ignore_old_datasource @ onFailure", throwable);
            dataSource.close();
        } else {
            mEventTracker.recordEvent(z ? Event.ON_DATASOURCE_FAILURE : Event.ON_DATASOURCE_FAILURE_INT);
            if (z) {
                this.logMessageAndFailure("final_failed @ onFailure", throwable);
                this.mDataSource = null;
                this.mHasFetchFailed = true;
                if (this.shouldRetryOnTap()) {
                    this.mSettableDraweeHierarchy.setRetry();
                } else {
                    this.mSettableDraweeHierarchy.setFailure();
                }
                this.getControllerListener().onFailure(this.mId, throwable);
                return;
            }
            this.logMessageAndFailure("intermediate_failed @ onFailure", throwable);
            this.getControllerListener().onIntermediateImageFailed(this.mId, throwable);
        }
    }

    static void onFailureInternal(AbstractDraweeController draweeController, String id, AbstractDataSource dataSource, Throwable throwable) {
        draweeController.onFailureInternal(id, dataSource, throwable, true);
    }

    private void logMessageAndFailure(String messageAndMethod, Throwable throwable) {
        if (FLog.isLoggable()) {
            FLog.a(TAG, "controller %x %s: %s: failure: %s",
                    System.identityHashCode(this),
                    this.mId,
                    messageAndMethod,
                    throwable);
        }
    }

    public final void release() {
        this.mEventTracker.recordEvent(Event.ON_RELEASE_CONTROLLER);
        if (this.mRetryManager != null) {
            this.mRetryManager.mTapToRetryAttempts = 0;
        }

        if (this.mGestureDetector != null) {
            this.mGestureDetector.reset();
        }

        if (this.mSettableDraweeHierarchy != null) {
            this.mSettableDraweeHierarchy.reset();
        }
        this.releaseFetch();
    }

    public final void addControllerListener(ControllerListener<? super INFO> controllerListener) {
        Preconditions.checkNotNull(controllerListener);
        if ((this.mControllerListener instanceof AbstractDraweeController.InternalForwardingListener)) {
            ((InternalForwardingListener<INFO>) this.mControllerListener).addListener(controllerListener);
        } else if (this.mControllerListener != null) {
            InternalForwardingListener<INFO> internalForwardingListener = new InternalForwardingListener();
            internalForwardingListener.addListener(mControllerListener);
            internalForwardingListener.addListener(controllerListener);
            this.mControllerListener = internalForwardingListener;
        } else {
            this.mControllerListener = (ControllerListener<INFO>) controllerListener;
        }
    }

    public final void setHierarchy(@Nullable DraweeHierarchy draweeHierarchy) {
        if (FLog.isLoggable()) {
            FLog.v(TAG, "controller %x %s: setHierarchy: %s", Integer.valueOf(System.identityHashCode(this)), this.mId, draweeHierarchy);
        }
        mEventTracker.recordEvent(draweeHierarchy != null ? Event.ON_SET_HIERARCHY : Event.ON_CLEAR_HIERARCHY);
        if (this.mIsRequestSubmitted) {
            this.mDeferredReleaser.cancelDeferredRelease(((DeferredReleaser.Releasable) this));
            this.release();
        }

        if (this.mSettableDraweeHierarchy != null) {
            this.mSettableDraweeHierarchy.setControllerOverlay();
            this.mSettableDraweeHierarchy = null;
        }

        if (draweeHierarchy != null) {
            Preconditions.checkArgument(draweeHierarchy instanceof SettableDraweeHierarchy);
            this.mSettableDraweeHierarchy = ((SettableDraweeHierarchy) draweeHierarchy);
            this.mSettableDraweeHierarchy.setControllerOverlay();
        }
    }

    public final boolean onTouchEvent(MotionEvent motionEvent) {
        if (FLog.isLoggable()) {
            FLog.v(TAG, "controller %x %s: onTouchEvent %s", Integer.valueOf(System.identityHashCode(this)), this.mId, motionEvent);
        }
        if (this.mGestureDetector != null && (this.mGestureDetector.mIsCapturingGesture || this.shouldRetryOnTap())) {
            switch (motionEvent.getAction()) {
                case 0: {
                    mGestureDetector.mIsCapturingGesture = true;
                    mGestureDetector.mIsClickCandidate = true;
                    mGestureDetector.mActionDownTime = motionEvent.getEventTime();
                    mGestureDetector.mActionDownX = motionEvent.getX();
                    mGestureDetector.mActionDownY = motionEvent.getY();
                    break;
                }
                case 1: {
                    mGestureDetector.mIsCapturingGesture = false;
                    if (Math.abs(motionEvent.getX() - mGestureDetector.mActionDownX) > mGestureDetector.mSingleTapSlopPx
                            || Math.abs(motionEvent.getY() - mGestureDetector.mActionDownY) > mGestureDetector.mSingleTapSlopPx) {
                        mGestureDetector.mIsClickCandidate = false;
                    }
                    if (!mGestureDetector.mIsClickCandidate) {
                        mGestureDetector.mIsClickCandidate = false;
                        break;
                    }
                    if (motionEvent.getEventTime() - mGestureDetector.mActionDownTime > (((long) ViewConfiguration.getLongPressTimeout()))) {
                        mGestureDetector.mIsClickCandidate = false;
                        break;
                    }
                    if (mGestureDetector.mClickListener == null) {
                        mGestureDetector.mIsClickCandidate = false;
                        break;
                    }
                    mGestureDetector.mClickListener.onClick();
                    mGestureDetector.mIsClickCandidate = false;
                    break;
                }
                case 2: {
                    if (Math.abs(motionEvent.getX() - mGestureDetector.mActionDownX) <= mGestureDetector.mSingleTapSlopPx
                            && Math.abs(motionEvent.getY() - mGestureDetector.mActionDownY) <= mGestureDetector.mSingleTapSlopPx) {
                        return true;
                    }
                    mGestureDetector.mIsClickCandidate = false;
                    break;
                }
                case 3: {
                    mGestureDetector.mIsCapturingGesture = false;
                    mGestureDetector.mIsClickCandidate = false;
                    break;
                }
            }
            return true;
        } else {
            return false;
        }
    }

    @Nullable
    public abstract INFO getImageInfo(T t);

    @Nullable
    public final DraweeHierarchy getHierarchy() {
        return this.mSettableDraweeHierarchy;
    }

    public int getImageHash(@Nullable T t) {
        return System.identityHashCode(t);
    }

    @Override
    public final void onAttach() {
        if (FLog.isLoggable()) {
            FLog.v(TAG, "controller %x %s: onAttach: %s", Integer.valueOf(System.identityHashCode(this)), mId, this.mIsRequestSubmitted ? "request already submitted" : "request needs submit");
        }

        this.mEventTracker.recordEvent(Event.ON_ATTACH_CONTROLLER);
        Preconditions.checkNotNull(this.mSettableDraweeHierarchy);
        this.mDeferredReleaser.cancelDeferredRelease(this);
        this.mIsAttached = true;
        if (!this.mIsRequestSubmitted) {
            this.submitRequest();
        }
    }

    @Override
    public final void onDetach() {
        if (FLog.isLoggable()) {
            FLog.a(TAG, "controller %x %s: onDetach", Integer.valueOf(System.identityHashCode(this)), this.mId);
        }
        this.mEventTracker.recordEvent(Event.ON_DETACH_CONTROLLER);
        this.mIsAttached = false;
        DeferredReleaser.ensureOnUiThread();
        if ((mDeferredReleaser.mPendingReleasables.add(this)) && mDeferredReleaser.mPendingReleasables.size() == 1) {
            mDeferredReleaser.mUiHandler.post(mDeferredReleaser.releaseRunnable);
        }
    }

    public final boolean onClick() {
        if (FLog.isLoggable()) {
            FLog.a(TAG, "controller %x %s: onClick", Integer.valueOf(System.identityHashCode(this)), this.mId);
        }

        if (this.shouldRetryOnTap()) {
            ++this.mRetryManager.mTapToRetryAttempts;
            this.mSettableDraweeHierarchy.reset();
            this.submitRequest();
            return true;
        } else {
            return false;
        }
    }

    public abstract AbstractDataSource getDataSource();

    private void releaseFetch() {
        this.mIsRequestSubmitted = false;
        this.mHasFetchFailed = false;
        if (this.mDataSource != null) {
            this.mDataSource.close();
            this.mDataSource = null;
        }

        this.mDrawable = null;
        if (this.mFetchedImage != null) {
            this.logMessageAndImage("release", this.mFetchedImage);
            this.mFetchedImage = null;
        }

        if (mIsRequestSubmitted) {
            this.getControllerListener().onRelease(this.mId);
        }
    }

    private ControllerListener getControllerListener() {
        return this.mControllerListener == null ? BaseControllerListener.getNoOpListener() : this.mControllerListener;
    }

    private boolean shouldRetryOnTap() {
        return this.mHasFetchFailed
                && this.mRetryManager != null
                && mRetryManager.mTapToRetryEnabled
                && mRetryManager.mTapToRetryAttempts < mRetryManager.mMaxTapToRetryAttempts;
    }

    private void submitRequest() {
        this.mEventTracker.recordEvent(Event.ON_DATASOURCE_SUBMIT);
        this.getControllerListener().onSubmit(this.mId, this.mCallerContext);
        this.mSettableDraweeHierarchy.setProgress();
        this.mIsRequestSubmitted = true;
        this.mHasFetchFailed = false;
        this.mDataSource = this.getDataSource();
        if (FLog.isLoggable()) {
            FLog.v(TAG, "controller %x %s: submitRequest: dataSource: %x", Integer.valueOf(System.identityHashCode(this)), this.mId, Integer.valueOf(System.identityHashCode(this.mDataSource)));
        }
        final boolean hasResult = this.mDataSource.hasResult();
        this.mDataSource.subscribe(new BaseDataSubscriber<T>() {
            public final void onNewResultImpl(AbstractDataSource<T> dataSource) {
                boolean isFinished = dataSource.isFinished();
                float progress = dataSource.getProgress();
                T result = dataSource.getResult();
                if (result != null) {
                    onNewResultInternal(mId, dataSource, result, progress, isFinished, hasResult);
                } else if (isFinished) {
                    AbstractDraweeController.onFailureInternal(AbstractDraweeController.this, mId, dataSource, new NullPointerException());
                }
            }

            public final void onFailureImpl(AbstractDataSource<T> dataSource) {
                AbstractDraweeController.onFailureInternal(AbstractDraweeController.this, mId, dataSource, dataSource.getFailureCause());
            }
        }, this.mUiThreadImmediateExecutor);
    }

    public String toString() {
        return android.support.design.a.toStringHelper(this)
                .add("isAttached", this.mIsAttached)
                .add("isRequestSubmitted", this.mIsRequestSubmitted)
                .add("hasFetchFailed", this.mHasFetchFailed)
                .add("fetchedImage", String.valueOf(this.getImageHash(this.mFetchedImage)))
                .add("events", this.mEventTracker.toString()).toString();
    }
}

